Skip to content

升級至 .NET 6 時多餘 AsQueryable() 呼叫的原因

TLDR

  • 在 .NET 6 專案中發現多餘的 AsQueryable() 呼叫,通常是因為從舊版專案升級而來。
  • 舊版 EF Core (5 以前) 的 DbSet<TEntity> 同時實作了 IQueryable<TEntity>IAsyncEnumerable<TEntity>,導致編譯器在處理 LINQ 擴充方法時產生歧義。
  • 呼叫 AsQueryable() 是為了明確指定型別為 IQueryable<TEntity>,以避開 System.Linq.Async 帶來的擴充方法衝突。
  • EF Core 6 移除了 DbSet<TEntity>IAsyncEnumerable<TEntity> 的直接實作,解決了此衝突,因此在 .NET 6 環境下可安全移除多餘的 AsQueryable()

問題情境與成因分析

在升級至 .NET 6 的專案中,開發者常會發現程式碼中充斥著 DbContext.Table.AsQueryable()。由於 DbSet<TEntity> 本身已經實作了 IQueryable<TEntity>,理論上無需額外呼叫 AsQueryable()

此現象主要發生在專案中同時引用了 System.Linq.Async 套件,且原始專案基於 EF Core 5 或更早版本的情況下。

擴充方法衝突的技術細節

在 EF Core 5 以前的版本,DbSet<TEntity> 的介面實作如下:

csharp
public abstract class DbSet<TEntity> : 
    Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<IServiceProvider>, 
    System.Collections.Generic.IAsyncEnumerable<TEntity>, 
    System.Collections.Generic.IEnumerable<TEntity>, 
    System.ComponentModel.IListSource, 
    System.Linq.IQueryable<TEntity> where TEntity : class

當專案引入 System.Linq.Async 時:

  • Queryable 類別定義了 IQueryable<TEntity> 的擴充方法(如 WhereSelect)。
  • AsyncEnumerable 類別定義了 IAsyncEnumerable<TSource> 的擴充方法(如 WhereSelect)。

由於 DbSet<TEntity> 同時實作了這兩個介面,編譯器無法判斷開發者意圖呼叫的是哪一個擴充方法,導致編譯錯誤。為了強制指定使用 IQueryable 的擴充方法,開發者必須顯式呼叫 AsQueryable()

EF Core 6 的改進

在 EF Core 6 及以後的版本中,DbSet<TEntity> 的介面實作已調整為:

csharp
public abstract class DbSet<TEntity> : 
    Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<IServiceProvider>, 
    System.Collections.Generic.IEnumerable<TEntity>, 
    System.ComponentModel.IListSource, 
    System.Linq.IQueryable<TEntity> where TEntity : class

結論與建議

由於 EF Core 6 移除了對 IAsyncEnumerable<TEntity> 的直接實作,編譯器不再面臨擴充方法選擇的歧義。即便專案中仍安裝有 System.Linq.Async,也不會再發生衝突。因此,在升級至 .NET 6 後,可以安全地移除這些多餘的 AsQueryable() 呼叫,以保持程式碼簡潔。

異動歷程

    • 初版文件建立。